#include <chrono>
#include <cstdio>
#include <thread>

#include <portmidi.h>
#include <porttime.h>

#define TRY_PM(EXPR)                                       \
    do {                                                   \
        PmError err__ = (EXPR);                            \
        if (err__ == pmNoError)                            \
            break;                                         \
        printf("%s: %s\n", #EXPR, Pm_GetErrorText(err__)); \
        return err__;                                      \
    } while (false)

enum Status {
    NOTE_ON = 0x90,
    NOTE_OFF = 0x80,
    CHANNEL = 0,
    NOTE = 60, // 中音 C
    V = 127, // 速度
    DURATION = 2000, // 持续时间，毫秒
};

class PmGuard {
public:
    PmError Prepare()
    {
        TRY_PM(Pm_Initialize());
        init_ = true;
        return pmNoError;
    }

    ~PmGuard()
    {
        if (init_)
            Pm_Terminate();
    }

private:
    bool init_ { false };
};

int main()
{
    TRY_PM(Pm_Initialize());
    PmGuard guard;

    TRY_PM(guard.Prepare());

    int c = Pm_CountDevices();
    PmDeviceID device = Pm_GetDefaultOutputDeviceID();

    printf("devices count: %d\n", c);
    printf("default output devices id: %d\n", device);

    PortMidiStream* stream;
    void* input_driver_info = NULL;
    int32_t buffer_size = 0;
    PmTimeProcPtr time_proc = NULL;
    void* time_info = NULL;
    int32_t latency = 0;

    TRY_PM(Pm_OpenOutput(&stream,
        device, input_driver_info, buffer_size,
        time_proc, time_info, latency));

    PmEvent note_on = {
        .message = Pm_Message(NOTE_ON | CHANNEL, NOTE, V),
        .timestamp = 0,
    };
    TRY_PM(Pm_Write(stream, &note_on, 1));

    Pt_Start(1, 0, 0);
    Pt_Sleep(DURATION);

    PmEvent note_off = {
        .message = Pm_Message(NOTE_OFF | CHANNEL, NOTE, V),
        .timestamp = 0,
    };
    TRY_PM(Pm_Write(stream, &note_off, 1));

    TRY_PM(Pm_Close(stream));
    return 0;
}
